﻿/*
 * The MIT License
 * 
 * Copyright 2008 Jeppe Burchardt and Pimm Hogeling
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following
 * conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 */

package org.dr.actionconsole.view
{
	import flash.display.CapsStyle;
	import flash.display.DisplayObject;
	import flash.display.LineScaleMode;
	import flash.display.Sprite;
	import flash.display.StageScaleMode;
	import flash.events.Event;
	import flash.events.KeyboardEvent;
	import flash.events.MouseEvent;
	import flash.text.AntiAliasType;
	import flash.text.TextField
	import flash.text.TextFieldType;
	import flash.text.TextFormat;
	import flash.ui.Keyboard;
	import org.dr.actionconsole.Console;
	import org.dr.actionconsole.data.CommandData;
	
	public class DefaultConsoleView extends Sprite implements IConsoleView
	{
		private const MODE_NORMAL : Boolean = false;
		private const MODE_SEARCH : Boolean = true;
		
		private var _mode : Boolean = false;
		private var input : TextField;
		private var output : TextField;
		private var query:String;
		private var scrollbar : Sprite;
		private var searchResult : TextField;
		private var closeButton:Sprite;
		
		/**
		* This is the default ConsoleView class. If you want to use your own ConsoleView class you can alter this one or write one
		*  from scratch.
		*/
		public function DefaultConsoleView() : void
		{
			input = new TextField();
			input.defaultTextFormat = new TextFormat("_sans", 9, 0xFFFFFF);
			input.antiAliasType = AntiAliasType.ADVANCED;
			input.type = TextFieldType.INPUT;
			addChild(input);
			
			output = new TextField();
			output.defaultTextFormat = new TextFormat("_sans", 9, 0xFFFFFF);
			output.antiAliasType = AntiAliasType.ADVANCED;
			output.wordWrap = true;
			output.useRichTextClipboard = true;
			addChild(output);
			
			scrollbar = new Sprite();
			scrollbar.buttonMode = true;
			addChild(scrollbar);
			
			closeButton = new Sprite();
			closeButton.buttonMode = true;
			//draw red square
			closeButton.graphics.beginFill(0xBA2B00);
			closeButton.graphics.drawRoundRect(0, 0, 15, 15, 3, 3);
			closeButton.graphics.endFill();
			//draw X shadow
			closeButton.graphics.lineStyle(4, 0x000000, 0.4);
			closeButton.graphics.moveTo(3, 3);
			closeButton.graphics.lineTo(11, 11);
			closeButton.graphics.moveTo(11, 3);
			closeButton.graphics.lineTo(3, 11);
			//draw X
			closeButton.graphics.lineStyle(2, 0xFFFFFF);
			closeButton.graphics.moveTo(3, 3);
			closeButton.graphics.lineTo(11, 11);
			closeButton.graphics.moveTo(11, 3);
			closeButton.graphics.lineTo(3, 11);
			//close console when clicked
			closeButton.addEventListener(MouseEvent.CLICK, close);
			addChild(closeButton);
			
			addEventListener(Event.ADDED_TO_STAGE, addedToStage);
		}
		/**
		 * Clears the console field.
		 */
		public function clearConsole() : void
		{
			output.text = "";
		}
		/**
		 * Returns the DisplayObject object that the console will add to the stage and try to keep on top of others.
		 */
		public function getDisplayObject() : DisplayObject
		{
			return this;
		}
		/**
		 * Returns what the user has inputted into he console. This method will be called when the enter key is pressed.
		 */
		public function getInput() : String
		{
			return input.text;
		}
		/**
		 * Sets the input of the console. This method will be called after the getInput method or when a user uses the history.
		 */
		public function setInput(value:String) : void
		{
			input.text = value;
			input.setSelection(value.length, value.length);
		}
		/**
		 * Opens or closes the console. True for open, false for close.
		 */
		public function setOpened(value:Boolean) : void
		{
			visible = value;
			if (value)
			{
				if (stage)
				{
					stage.focus = input;
				}
			}
		}
		/**
		 * Writes some text to the console.
		 */
		public function write(text:String, color:uint) : void
		{
			const start : uint = output.length;
			output.appendText(text);
			
			output.setTextFormat(new TextFormat(null, null, color), start, output.length);
			output.scrollV = output.maxScrollV;
			
			//update the search results
			if (_mode === MODE_SEARCH)
			{
				search(input.text, true);
			}
		}
		private function addedToStage(e:Event) : void
		{
			paint();
			
			stage.focus = input;
			scrollbar.addEventListener(MouseEvent.MOUSE_DOWN, startScroll);
			scrollbar.addEventListener(MouseEvent.MOUSE_UP, stopScroll);
			output.addEventListener(Event.SCROLL, updateScrollbar);
			stage.addEventListener(Event.RESIZE, paint);
			stage.addEventListener(KeyboardEvent.KEY_UP, keyUp, true);
		}
		private function close(e:MouseEvent) : void
		{
			Console.close();
		}
		private function inputChange(event:Event) : void
		{
			search(input.text);
		}
		private function keyUp(e:KeyboardEvent):void 
		{
			if (e.charCode === 102 && e.ctrlKey && visible) 
			{
				mode = !_mode;
				setInput("");
				input.setSelection(0, 0);
				paint();
			}
		}
		private function get mode():Boolean
		{
			return _mode;
		}
		private function set mode(value:Boolean):void 
		{
			if (!searchResult)
			{
				searchResult = new TextField();
				searchResult.defaultTextFormat = new TextFormat("_sans", 9, 0xFFFFFF);
				searchResult.antiAliasType = AntiAliasType.ADVANCED;
				searchResult.wordWrap = true;
				searchResult.useRichTextClipboard = true;
				addChild(searchResult);
				//move closebutton to top
				swapChildren(closeButton, searchResult);
			}
			if (value === MODE_NORMAL)
			{
				input.removeEventListener(Event.CHANGE, inputChange);
				
				searchResult.removeEventListener(Event.SCROLL, updateScrollbar);
				searchResult.visible = false;
				
				output.addEventListener(Event.SCROLL, updateScrollbar);
				output.visible = true;
			}
			else
			{
				input.addEventListener(Event.CHANGE, inputChange);
				
				output.removeEventListener(Event.SCROLL, updateScrollbar);
				output.visible = false;
				
				searchResult.addEventListener(Event.SCROLL, updateScrollbar);
				searchResult.visible = true;
			}
			
			_mode = value;
		}
		private function paint(event:Event=null) : void
		{
			input.x = (_mode === MODE_NORMAL ? 3 : 16)
			input.y = stage.stageHeight - 16;
			input.height = 16;
			input.width = stage.stageWidth - 6;
			
			output.x = output.y = 3;
			output.width = stage.stageWidth - 21;
			output.height = stage.stageHeight - 16;
			
			if (searchResult)
			{
				searchResult.x = searchResult.y = 3;
				searchResult.width = stage.stageWidth - 6;
				searchResult.height = stage.stageHeight - 16;
			}
			
			graphics.clear();
			graphics.beginFill(0x000000, 0.7);
			graphics.drawRect(0, 0, stage.stageWidth, stage.stageHeight - 16);
			graphics.endFill();
			
			graphics.beginFill((_mode == MODE_NORMAL ? 0x333333 : 0x006600), 0.7);
			graphics.drawRect(0, stage.stageHeight - 16, stage.stageWidth, 16);
			graphics.endFill();
			
			scrollbar.x = stage.stageWidth - 2;
			
			if (_mode === MODE_SEARCH)
			{
				graphics.lineStyle(2, 0xFFFFFF);
				graphics.drawCircle(7, stage.stageHeight - 16 + 7, 4);
				graphics.moveTo(9.5, stage.stageHeight - 16 + 9.5);
				graphics.lineTo(13, stage.stageHeight - 16 + 13);
			}
			
			closeButton.y = 2;
			
			updateScrollbar();
		}
		private function scroll(event:Event=null) : void
		{
			var scrollTarget : TextField = (_mode === MODE_NORMAL ? output : searchResult);
			scrollTarget.scrollV = (scrollbar.mouseY / (scrollbar.height - 10)) * scrollTarget.maxScrollV;
		}
		private function search(query:String, keepScroll:Boolean=false) : void
		{
			if (query !== "")
			{
				var deltaScroll : Number = searchResult.scrollV;
				var text : String = output.text;
				var lines : Array = text.split("\r");
				searchResult.text = "Results for \""+query+"\": ("+lines.length +" lines)\n";
				this.query = query;
				lines.forEach(searchForEach);
				if (keepScroll)
				{
					searchResult.scrollV = deltaScroll;
				}
				this.query = null;
			}
			else
			{
				searchResult.text = "";
			}
		}
		private function searchForEach(item:String, i:uint, a:Array) : void
		{
			if (item.toLowerCase().indexOf(query.toLowerCase()) > -1)
			{
				searchResult.appendText(i.toString() + "\t\t" + item + "\n");
			}
		}
		private function startScroll(e:Event):void
		{
			scroll();
			stage.addEventListener(MouseEvent.MOUSE_MOVE, scroll);
			stage.addEventListener(MouseEvent.MOUSE_UP, stopScroll);
		}
		private function stopScroll(e:Event):void
		{
			stage.removeEventListener(MouseEvent.MOUSE_MOVE, scroll);
			stage.removeEventListener(MouseEvent.MOUSE_UP, stopScroll);
		}
		private function updateScrollbar(event:Event=null) : void
		{
			var scrollTarget : TextField = (_mode === MODE_NORMAL ? output : searchResult);
			
			const scrollbarHeight : int = stage.stageHeight - 17;
			const scrollbarSliderHeight : Number = (scrollbarHeight / scrollTarget.textHeight) * scrollbarHeight;
			
			const scrollbarSliderY : Number = (scrollbarHeight - scrollbarSliderHeight) * (scrollTarget.scrollV - 1) / (scrollTarget.maxScrollV - 1);
			
			scrollbar.graphics.clear();
			scrollbar.graphics.lineStyle(5, 0x000000, 0.5, true, LineScaleMode.NORMAL, CapsStyle.SQUARE);
			scrollbar.graphics.moveTo(0, 2);
			scrollbar.graphics.lineTo(0, stage.stageHeight - 19);
			
			scrollbar.graphics.lineStyle(3, 0xFFFFFF, 0.5, true, LineScaleMode.NORMAL, CapsStyle.SQUARE);
			scrollbar.graphics.moveTo(0, scrollbarSliderY + 2);
			scrollbar.graphics.lineTo(0, scrollbarSliderY + scrollbarSliderHeight - 2);
			
			scrollbar.visible = (scrollbarSliderHeight < scrollbarHeight);
			
			closeButton.x = stage.stageWidth - closeButton.width - (scrollbar.visible ? 6 : 2);
		}
	}
}